package sindconsole.auth.webapp.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import common.annotation.Traced;
import common.exception.BadRequestException;
import common.exception.ForbiddenException;
import common.exception.NotFoundException;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import sindconsole.auth.bean.*;
import sindconsole.auth.service.AuthService;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.Principal;
import java.util.Date;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public @ResponseBody
    String handleException(DuplicateKeyException x) {
        log.warn(x.getMessage());
        return x.getMessage();
    }

    @Autowired
    AuthService authService;

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class WholeUser extends User {
        @ExcelProperty("登录名")
        String username;
        @ExcelProperty("密码")
        String password;
    }

    @GetMapping("/users")
    @RequiresPermissions("users:r")
    public Object listUsers(@RequestParam(value = "keyword", required = false) String keyword,
                            @RequestParam(value = "createdTime0", required = false)  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date createdTime0,
                            @RequestParam(value = "createdTime1", required = false)  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date createdTime1,
                            @RequestParam(value = "page", defaultValue = "0") int page,
                            @RequestParam(value = "pageSize", required = false) Integer pageSize) {
        int[] pagination = pageSize != null ? new int[] {page, pageSize} : new int[] {page};
        return authService.findUsers(keyword, createdTime0, createdTime1, pagination);
    }

    @GetMapping("/users|export")
    @RequiresPermissions("users:r")
    @ApiOperation("批量导出用户")
    public void exportUsers(HttpServletResponse response,
                            @RequestParam(value = "keyword", required = false) String keyword,
                            @RequestParam(value = "createdTime0", required = false) Date createdTime0,
                            @RequestParam(value = "createdTime1", required = false) Date createdTime1,
                            @RequestParam(value = "fileName", defaultValue = "用户") String fileName) throws IOException {
        Page<User> users = authService.findUsers(keyword, createdTime0, createdTime1);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
        EasyExcel.write(response.getOutputStream(), WholeUser.class)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .sheet(fileName).doWrite(users.getContent());
    }

    @GetMapping("/users|find")
    @RequiresPermissions("users:r")
    public Object findUsers(@RequestParam(value = "keyword", required = false) String keyword) {
        return authService.findUsers(keyword, null, null);
    }

    @GetMapping("/userinfo/{id}")
    public Object userinfo(@PathVariable("id") long id) {
        return authService.findUser(id);
    }

    @Data
    public static class UserDetail {
        User user;
        List<Account> accounts;
        List<Role> roles;
        List<Permission> permissions;
    }

    @GetMapping("/users/{id}")
    @RequiresPermissions("users:r")
    public Object detailUser(@PathVariable("id") long id) {
        User user = authService.findUser(id);
        if(user == null) throw new NotFoundException();
        UserDetail result = new UserDetail();
        result.setUser(user);
        result.setAccounts(authService.findUserAccounts(id));
        result.setPermissions(authService.findUserPermissions(id));
        result.setRoles(authService.findUserRoles(id));
        return result;
    }

    @GetMapping("/me")
    @RequiresAuthentication
    public Object detailMe(@ApiIgnore User subjectUser) {
        UserDetail result = new UserDetail();
        result.setUser(subjectUser);
        result.setAccounts(authService.findUserAccounts(subjectUser.getId()));
        result.setPermissions(authService.findUserPermissions(subjectUser.getId()));
        result.setRoles(authService.findUserRoles(subjectUser.getId()));
        return result;
    }

    @PostMapping("/users")
    @RequiresPermissions("users:rw")
    @Traced("创建用户")
    public Object createUser(@RequestBody WholeUser user) {
        try {
            return authService.createUserAndAccount(user, user.username, user.password);
        } catch(DuplicateKeyException x) {
            throw new DuplicateKeyException("该登录名已存在");
        }
    }

    @PostMapping("/users/{id}")
    @RequiresPermissions("users:rw")
    @Traced("修改用户")
    public Object updateUser(@PathVariable("id") long id,
                             @RequestBody WholeUser user) {
        user.setId(id);
        return authService.updateUserAndPassword(user, user.username, user.password);
    }

    @PostMapping(value = "/users|import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @RequiresPermissions("users:rw")
    @ApiOperation("批量导入用户")
    @Traced
    public Object importUsers(@RequestPart("file") MultipartFile file) throws IOException {
        final int[] n = new int[] { 0, 0 };
        EasyExcel.read(file.getInputStream(), WholeUser.class, new ReadListener<WholeUser>() {
            @Override
            public void invoke(WholeUser user, AnalysisContext analysisContext) {
                try {
                    authService.createUserAndAccount(user, user.username, user.password);
                    n[0] += 1;
                } catch(Exception x) {
                    log.warn(x.getMessage(), x);
                    n[1] += 1;
                }
            }
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            }
        }).sheet().doRead();
        return n;
    }

    @PostMapping("/users/{id}/status")
    @RequiresPermissions("users:rw")
    @Traced("冻结（或解除冻结）用户")
    public Object setUserStatus(@PathVariable("id") long id,
                                @RequestParam("status") Short status) {
        User user = authService.findUser(id);
        user.setStatus(status);
        return authService.updateUser(user);
    }

    @PostMapping("/me")
    @RequiresAuthentication
    @Traced
    public Object updateMe(@RequestBody WholeUser user,
                           @ApiIgnore User subjectUser) {
        if(!user.getId().equals(subjectUser.getId())) throw new ForbiddenException();
        if(StringUtils.hasText(user.getPhone()) && !user.getPhone().equals(subjectUser.getPhone())) user.setPhoneStatus((short) 0);
        if(StringUtils.hasText(user.getMail()) && !user.getMail().equals(subjectUser.getMail())) user.setMailStatus((short) 0);
        int r = authService.updateUser(user);
        updatePrincipal(user);
        return r;
    }

    static void updatePrincipal(User user) {
        Session session = SecurityUtils.getSubject().getSession();
        PrincipalCollection pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
        UserPrincipal userPrincipal = (UserPrincipal)pc.getPrimaryPrincipal();
        userPrincipal.setUser(user);
        session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, pc);
    }

    @Data
    static class Passwords {
        String oldPassword;
        String password;
        String confirmPassword;
    }

    @PostMapping("/my/password")
    @RequiresAuthentication
    @Traced
    public Object updateMyPassword(@RequestBody Passwords passwords, @ApiIgnore User subjectUser) {
        if(!StringUtils.hasText(passwords.getPassword()) || !passwords.getPassword().equals(passwords.getConfirmPassword()))
            throw new BadRequestException("密码/确认密码不一致");
        User confirmedUser = authService.authc(((Principal)SecurityUtils.getSubject().getPrincipal()).getName(), passwords.getOldPassword());
        if(confirmedUser == null || !confirmedUser.getId().equals(subjectUser.getId()))
            throw new ForbiddenException();
        int r = authService.updateUserPassword(subjectUser.getId(), passwords.getPassword());
        //SecurityUtils.getSubject().logout();
        return r;
    }

    @GetMapping("/permissions")
    @RequiresPermissions("users:r")
    public Object listPermissions() {
        return authService.findPermissions();
    }

    @GetMapping("/roles")
    @RequiresPermissions("users:r")
    public Object listRoles() {
        return authService.findRoles();
    }

    @Data
    public static class RoleDetial {
        Role role;
        List<Permission> permissions;
    }

    @GetMapping("/roles/{key}")
    @RequiresPermissions("users:r")
    public Object detailRole(@PathVariable("key") String key) {
        Role role = authService.findRole(key);
        if(role == null) throw new NotFoundException();
        RoleDetial result = new RoleDetial();
        result.setRole(role);
        result.setPermissions(authService.findRolePermission(key));
        return result;
    }

    @PostMapping("/roles")
    @RequiresPermissions("users:rw")
    @Traced("创建角色")
    public Object createRole(@RequestBody Role role) {
        try {
            role.setPredefined(false);
            return authService.createRole(role);
        } catch(DuplicateKeyException x) {
            throw new DuplicateKeyException("该角色值已存在");
        }
    }

    @PostMapping("/roles/{key}")
    @RequiresPermissions("users:rw")
    @Traced("修改角色")
    public Object updateRole(@PathVariable("key") String key,
                             @RequestBody Role role) {
        role.setPredefined(false);
        return authService.updateRole(role, key);
    }

    @PostMapping("/roles/{key}|remove")
    @RequiresPermissions("users:rw")
    @Traced("删除角色")
    public Object removeRole(@PathVariable("key") String key) {
        return authService.deleteRole(key);
    }

    @PostMapping("/user/{id}|grant")
    @RequiresPermissions("users:rw")
    @Traced("授权用户")
    public Object grantUser(@PathVariable("id") long id,
                            @RequestParam(value = "permissionKey") String[] permissionKeys,
                            @RequestParam(value = "roleKey") String[] roleKeys) {
        int r = authService.ungiveUserRoles(id);
        r += authService.ungiveUserPermissions(id);
        if(!ArrayUtils.isEmpty(permissionKeys))
            r += authService.giveUserPermissions(id, permissionKeys);
        if(!ArrayUtils.isEmpty(roleKeys))
            r += authService.giveUserRoles(id, roleKeys);
        return r;
    }

    @PostMapping("/role/{key}|grant")
    @RequiresPermissions("users:rw")
    @Traced("授权角色")
    public Object grantRole(@PathVariable("key") String key,
                            @RequestParam(value = "permissionKey") String[] permissionKeys) {
        int r = authService.ungiveRolePermissions(key);
        if(!ArrayUtils.isEmpty(permissionKeys))
            r += authService.giveRolePermissions(key, permissionKeys);
        return r;
    }

    @GetMapping("/accounts")
    @RequiresPermissions("users:r")
    public Object listUserAccounts(@RequestParam(value = "keyword", required = false) String keyword,
                                   @RequestParam(value = "page", defaultValue = "0") int page,
                                   @RequestParam(value = "pageSize", required = false) Integer pageSize) {
        int[] pagination = pageSize != null ? new int[] {page, pageSize} : new int[] {page};
        return authService.findUserAccount(keyword, pagination);
    }

}
